#pragma once
#include <vector>
#include <algorithm>
#include <functional>
#include <boost/iterator/zip_iterator.hpp>
#include <boost/iterator/counting_iterator.hpp>
#include <GL/gl.h>
#include <Ptexture.h>
#include "matrix.h"
#include "patch.h"
#include "types.h"

using namespace std;
typedef pair<int, int> edge;

typedef std::vector<int> index_array;
typedef std::vector<vertex> vertex_array;
typedef std::vector<edge> edge_array;
typedef std::vector<vec2f> coord_array;
typedef index_array face_array;
typedef index_array offset_array;

inline void glVertex3f(const vertex& v) { glVertex3f(v[0], v[1], v[2]); }
inline void glColor3f(const vertex& v) { glColor3f(v[0], v[1], v[2]); }

template <class Operation, class Itr>
void indexed_for(Itr a, Itr b, Operation op) {
    auto begin = boost::make_zip_iterator(boost::make_tuple(boost::counting_iterator<int>(0), a));
    auto end = boost::make_zip_iterator(boost::make_tuple(boost::counting_iterator<int>(distance(a, b)), b));
    for_each(begin, end, [&op](const boost::tuple<const int&, const typename Itr::value_type& >& t) { op(t.get<0>(), t.get<1>()); });
}

template <class Operation, class Itr1, class Itr2>
void indexed_for(Itr1 begin1, Itr1 end1, Itr2 begin2, Itr2 end2, Operation op) {
    auto begin = boost::make_zip_iterator(boost::make_tuple(boost::counting_iterator<int>(0), begin1, begin2));
    auto end = boost::make_zip_iterator(boost::make_tuple(boost::counting_iterator<int>(distance(begin1, end1)), end1, end2));
    for_each(begin, end, [&op](const boost::tuple<const int&, const typename Itr1::value_type&, const typename Itr2::value_type& >& t) { op(t.get<0>(), t.get<1>(), t.get<2>()); });
}

template <class Operation, class In1, class In2, class In3, class Out>
void transform(In1 begin1, In1 end1, In2 begin2, In3 begin3, Out out, Operation op) {
    auto d = distance(begin1, end1);
    auto begin = boost::make_zip_iterator(boost::make_tuple(begin1, begin2, begin3));
    auto end = boost::make_zip_iterator(boost::make_tuple(end1, begin2 + d, begin3 + d));
    transform(begin, end, out, [&op](const boost::tuple<typename In1::value_type&, typename In2::value_type&, typename In3::value_type& >& t) -> typename Out::value_type { return op(t.get<0>(), t.get<1>(), t.get<2>()); });
}

class mesh {
    public:
        mesh() { }
        mesh(int n_vertices, int n_edges, int n_faces) : 
            vertices(n_vertices, vertex{0,0,0}), 
            coords(n_edges, vec2f{0,0}),
            edges(n_edges), 
            faces(n_edges),
            offsets(n_faces + 1),
            adjedges(n_edges),
            parents(n_edges) { }

        void render(float t, bool smooth);
        void prepare() { build_adjacency(); init_coords(); }
        void add_vertex(const vertex& v) { vertices.push_back(v); }
        void add_edge(edge e) { edges.push_back(e); }
        void add_face(int f) { faces.push_back(f); }
        void add_offset(int o) { offsets.push_back(o); }
        void add_parent(int p) { parents.push_back(p); }
        void init_coords();
        mesh subdivide(void);
        void to_patches(std::stack<primitive_ptr>& primitives);
        segment sline(float& x, float& y, float& line_angle, int& start_edge, float& len);
        uv get_uv(int edge_id, float u, float v);
        vec2f get_st(int face_id, const vec2f& uv);
        void write_ptex(float tr);
        
    private:
        vertex_array vertices;
        vertex_array normals;
        coord_array coords;
        edge_array edges;
        index_array faces;
        index_array offsets;
        index_array adjedges;
        index_array singular;
        index_array parents;

        vector<normal> face_vertex_normals(int face_id);
        vector<normal> smooth_normals();
        void build_adjacency();
        index_array build_edgepoint_indices();
        int count_edges(int face_id) { return offsets[face_id + 1] - offsets[face_id]; }
        int count_faces(void) { return offsets.size() - 1; }
        int edge_parent(int edge_id) { return parents[edge_id]; }
        vertex& face_vertex(int edge_id) { return vertices[edges[faces[edge_id]].first]; }
        vertex& face_vertex(int face_id, int edge_id) { return face_vertex(offsets[face_id] + edge_id); }
        vertex& first_vertex(int edge_id) { return vertices[edges[faces[edge_id]].first]; }
        vertex& second_vertex(int edge_id) { return vertices[edges[faces[edge_id]].second]; }
        normal& first_normal(int edge_id) { return normals[edges[faces[edge_id]].first]; }
        normal& second_normal(int edge_id) { return normals[edges[faces[edge_id]].second]; }
        vec2f& pcoord(int edge_id) { return coords[faces[edge_id]]; }
        int face_vertex_id(int edge_id) { return edges[faces[edge_id]].first; }
        int face_vertex_id(int face_id, int edge_id) { return face_vertex_id(offsets[face_id] + edge_id); }
        int opposite_edge(int edge_id) { return adjedges[edge_id]; }
        int previous_edge(int edge_id) { return previous_edge(edge_parent(edge_id), edge_id); }
        int previous_edge(int face_id, int edge_id) { if ((edge_id - 1) < offsets[face_id]) return offsets[face_id+1]-1; else return edge_id - 1; }
        int next_edge(int edge_id) { return next_edge(edge_parent(edge_id), edge_id); }
        int next_edge(int face_id, int edge_id) { if ((edge_id + 1) >= offsets[face_id+1]) return offsets[face_id]; else return edge_id + 1; }
};
